﻿using DotNetExtensions.Core.Invoker;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace DotNetExtensions.Core.Helpers
{
    public class TypePropertyParser
    {
        private readonly Mapper.GenericMapper<TypePropertyTree, TypePropertyItem> _mapper;
        public TypePropertyParser()
        {
            _mapper = new Mapper.GenericMapper<TypePropertyTree, TypePropertyItem>();
        }
        public List<TypePropertyTree> Parse(Type type, string name = null, object defaultValue = null, string description = null)
        {
            List<TypePropertyTree> parameters = new List<TypePropertyTree>();
            TypePropertyTree root = new TypePropertyTree()
            {
                Description = description,
                Guid = Guid.NewGuid().ToString(),
                IsArrayList = false,
                Name = name,
            };
            parameters.Add(root);
            if (TypeHelper.IsBasicType(type))
            {
                root.PropertyType = type;
                root.IsBasicType = true;
                if (defaultValue == null) defaultValue = TypeHelper.DefaultValue(type);
                root.DefaultValue = defaultValue;
            }
            else if (TypeHelper.IsArray(type))
            {
                Type gt = Type.GetType(type.FullName.Replace("[]", string.Empty));

                root.IsArrayList = true;
                root.PropertyType = gt;

                if (!TypeHelper.IsBasicType(gt))
                {
                    root.InnerParameters = ParseClass(gt, name, defaultValue, description);
                }
            }
            else if (TypeHelper.IsEnumerable(type))
            {
                Type gt = type.GetGenericArguments().FirstOrDefault();
                if (string.IsNullOrEmpty(root.Description))
                {
                    DescriptionAttribute descAttribute = gt.GetCustomAttribute<DescriptionAttribute>();
                    description = descAttribute?.Description ?? "";
                    root.Description = description;
                }
                root.IsArrayList = true;
                root.PropertyType = gt;

                if (!TypeHelper.IsBasicType(gt))
                {
                    root.InnerParameters = ParseClass(gt, name, defaultValue, description);
                }
            }
            else if (type.IsClass)
            {
                if (string.IsNullOrEmpty(root.Description))
                {
                    DescriptionAttribute descAttribute = type.GetCustomAttribute<DescriptionAttribute>();
                    description = descAttribute?.Description ?? "";
                    root.Description = description;
                }
                root.PropertyType = type;
                root.InnerParameters = ParseClass(type, name, defaultValue, description);
            }
            return parameters;
        }
        private List<TypePropertyTree> ParseClass(Type type, string name, object defaultValue, string description)
        {
            if (type == null) return null;
            List<TypePropertyTree> parameters = new List<TypePropertyTree>();
            if (TypeHelper.IsBasicType(type))
            {
                TypePropertyTree node = new TypePropertyTree()
                {
                    DefaultValue = TypeHelper.DefaultValue(type),
                    Description = null,
                    Guid = Guid.NewGuid().ToString(),
                    IsArrayList = false,
                    Name = name,
                    PropertyType = type,
                    IsBasicType = true
                };
                parameters.Add(node);
            }
            else if (type.IsClass)
            {
                foreach (var item in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    DescriptionAttribute descAttribute = item.GetCustomAttribute<DescriptionAttribute>();
                    description = descAttribute?.Description ?? "";
                    defaultValue = descAttribute?.DefaultValue ?? null;
                    TypePropertyTree node = new TypePropertyTree()
                    {
                        Description = description,
                        Guid = Guid.NewGuid().ToString(),
                        IsArrayList = false,
                        Name = item.Name,
                        PropertyType = item.PropertyType
                    };
                    parameters.Add(node);
                    if (TypeHelper.IsBasicType(item.PropertyType) || item.PropertyType == typeof(object))
                    {
                        node.IsBasicType = true;
                        node.DefaultValue = defaultValue;
                    }
                    else if (TypeHelper.IsArray(item.PropertyType))
                    {
                        Type gt = Type.GetType(item.PropertyType.FullName.Replace("[]", string.Empty));
                        node.IsArrayList = true;
                        node.PropertyType = gt;

                        if (!TypeHelper.IsBasicType(gt))
                        {
                            node.InnerParameters = ParseClass(gt, item.Name, defaultValue, description);
                        }
                    }
                    else if (TypeHelper.IsEnumerable(item.PropertyType))
                    {
                        Type gt = item.PropertyType.GetGenericArguments().FirstOrDefault();
                        node.IsArrayList = true;
                        node.PropertyType = gt;

                        if (!TypeHelper.IsBasicType(gt))
                        {
                            node.InnerParameters = ParseClass(gt, item.Name, defaultValue, description);
                        }
                    }
                    else if (item.PropertyType.IsClass)
                    {
                        node.InnerParameters = ParseClass(item.PropertyType, item.Name, defaultValue, description);
                    }
                }
            }
            return parameters;
        }

        public List<TypePropertyTree> Parse(PropertyTypeInfo typeInfo)
        {
            if (typeInfo == null) return new List<TypePropertyTree>();
            return Parse(typeInfo.Type, typeInfo.Name, typeInfo.Value, typeInfo.Description);
        }
        public List<TypePropertyTree> Parse(PropertyTypeInfo[] typeInfos)
        {
            List<TypePropertyTree> parameters = new List<TypePropertyTree>();
            if (typeInfos == null || typeInfos.Length == 0) return parameters;

            foreach (var item in typeInfos)
            {
                parameters.AddRange(Parse(item));
            }

            return parameters;
        }

        public List<TypePropertyItem> ToList(List<TypePropertyTree> treeItems)
        {
            List<TypePropertyItem> items = new List<TypePropertyItem>();
            foreach (var item in treeItems)
            {
                items.Add(_mapper.Mapper(item));
                if (item.InnerParameters != null && item.InnerParameters.Any())
                {
                    foreach (var sub in item.InnerParameters) sub.ParentGuid = item.Guid;
                    var subs = ToList(item.InnerParameters);
                    
                    items.AddRange(subs);
                }
            }
            return items;
        }
        public List<TypePropertyItem> ParseList(PropertyTypeInfo[] typeInfos)
        {
            return ToList(Parse(typeInfos));
        }
        public List<TypePropertyItem> ParseList(PropertyTypeInfo typeInfo)
        {
            return ToList(Parse(typeInfo));
        }
        public List<TypePropertyTree> Parse(string json, TypePropertyTree root = null)
        {
            List<TypePropertyTree> typeProperties = new List<TypePropertyTree>();
            if (string.IsNullOrEmpty(json)) return typeProperties;
            JToken jToken = JToken.Parse(json);
            if (jToken.Type == JTokenType.Array)
            {
                JToken item = jToken.ToArray().FirstOrDefault();
                if(item != null)
                {
                    Type t = JTokenTypeConverter.Change(item.Type);
                    if (t == null || TypeHelper.IsBasicType(t))
                    {
                        root.DefaultValue = item.ToObject(t);
                        root.PropertyType = t;
                        root.IsArrayList = true;
                    }
                    else
                    {
                        typeProperties.AddRange(Parse(item.ToString(), root));
                    }
                }
            }
            else if (jToken.Type == JTokenType.Object)
            {
                var properties = ((JObject)jToken).Properties();
                foreach (JProperty property in properties)
                {
                    TypePropertyTree typeProperty = new TypePropertyTree()
                    {
                        DefaultValue = null,
                        Description = null,
                        Guid = Guid.NewGuid().ToString(),
                        Name = property.Name,
                        PropertyType = typeof(object),
                        IsArrayList = false
                    };
                    typeProperties.Add(typeProperty);
                    if (property.Value != null)
                    {
                        Type t = JTokenTypeConverter.Change(property.Value.Type);
                        if (t == null || TypeHelper.IsBasicType(t))
                        {
                            bool hasValue = property.HasValues;
                            object value = ((JValue)property.Value).Value;
                            if (value != null && t != value.GetType()) t = value.GetType();
                            typeProperty.PropertyType = hasValue && t != null ? t : typeof(string);
                            typeProperty.IsBasicType = true;
                            typeProperty.DefaultValue = hasValue && value != null? property.Value.ToObject(t) : null;
                        }
                        else if (TypeHelper.IsArray(t))
                        {
                            typeProperty.IsArrayList = true;
                            typeProperty.InnerParameters = Parse(property.Value.ToString(), typeProperty);
                        }
                        else if (t == typeof(object))
                        {
                            typeProperty.InnerParameters = Parse(property.Value.ToString(), typeProperty);
                        }
                    }
                }
            }
            else { throw new Exception("不支持的数据格式"); }

            return typeProperties;
        }
    }
}
